package com.youlu.campus.web.qq;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.youlu.campus.web.common.base.RequestContext;
import com.youlu.campus.base.exception.BusinessException;
import com.youlu.campus.service.utils.HttpClientUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 内容检查接口文档。
 * https://q.qq.com/wiki/develop/miniprogram/server/open_port/port_safe.html#security-imgseccheck
 */
@Component
@Slf4j
public class QQService {

    @Value("${sys.qq.appId}")
    public String appId;

    @Value("${sys.qq.secret}")
    public String tokenKey;

    private String accessTokenKey = "QQ-TokenKey";

    static Long FLAG = 500L;

    @Autowired
    private StringRedisTemplate redisTemplate;

    public void clearTokenCache() {
        redisTemplate.delete(accessTokenKey);

    }

    public String getAccessToken() {
        log.info("qq redis key is {}", accessTokenKey);
        String key = redisTemplate.opsForValue().get(accessTokenKey);
        if (StringUtils.isBlank(key)) {
            String content = getTokenJsonFromQQ();
            AccessTokenResult tokenResult = JSON.parseObject(content, AccessTokenResult.class);
            if (tokenResult.getErrcode() != 0) {
                log.info("获取AccessToken错误,错误信息:{}", tokenResult.getErrmsg());
                throw new BusinessException(tokenResult.getErrmsg());
            } else {
                //请求成功
                redisTemplate.opsForValue().set(accessTokenKey, tokenResult.getAccessToken());
                //根据返回凭证有效时间再减去500秒作为redis存储有效时间
                Long expiresTime;
                if (tokenResult.getExpiresIn() < FLAG) {
                    expiresTime = tokenResult.getExpiresIn();
                } else {
                    expiresTime = tokenResult.getExpiresIn() - FLAG;
                }
                redisTemplate.expire(accessTokenKey, expiresTime, TimeUnit.SECONDS);
                log.info("accessToken存储到redis成功，有效时间:{}", expiresTime);
                return tokenResult.getAccessToken();
            }
        } else {
            return key;
        }
    }

    /**
     * 图片安全检测。
     * @param token
     * @param resource
     * @return true 图片正常，没有违规。
     */
    public boolean checkImage(String token, Resource resource) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);

        MultiValueMap<String, Object> map= new LinkedMultiValueMap<String, Object>();
        map.add("access_token", token);
        map.add("appid", appId);

        map.add("media", resource);

        HttpEntity<MultiValueMap<String, Object>> request =
                new HttpEntity<MultiValueMap<String, Object>>(map, headers);

        String url = "https://api.q.qq.com/api/json/security/ImgSecCheck?" +
                "access_token="+token;
        ResponseEntity<String> response = HttpClientUtils.getRestTemplate()
                .postForEntity( url, request , String.class );
        String body = response.getBody();
        log.info("img check {}, {}", response.getStatusCode(), body.replace("\n", ""));

        JSONObject json = JSON.parseObject(body);
        // 87014	内容含有违法违规内容
        return json.getIntValue("errCode") != 87014;
    }

    /**
     * 检查文本。
     * @param token
     * @param content
     * @return true 正常，无违规词。
     */
    public boolean msgCheckBoolean(String token, String content) {
        String str = msgCheck(token, content);
        JSONObject json = JSONObject.parseObject(str);
        boolean ret = json.getIntValue("errCode") != 87014;
        if (!ret) {
            log.warn("检查到敏感内容。{} 用户 {}"
                    , content, RequestContext.getCurUser().getOpenId());
        }
        return ret;
        /*
        {
          "errCode": 87014,
          "errMsg": "risky content"
        }
         */
    }

    /**
     * https://q.qq.com/wiki/develop/miniprogram/server/open_port/port_safe.html#security-msgseccheck
     * @param token
     * @param content
     * @return
     */
    public String msgCheck(String token, String content) {
        String url = "https://api.q.qq.com/api/json/security/MsgSecCheck?access_token=";
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        JSONObject personJsonObject = new JSONObject();
        personJsonObject.put("access_token", token);
        personJsonObject.put("appid", appId);
        personJsonObject.put("content", content);

        HttpEntity<String> request =
                new HttpEntity<String>(personJsonObject.toString(), headers);

        String retStr = HttpClientUtils.getRestTemplate().postForObject(url, request, String.class);
        return retStr;
    }

    /**
     * https://q.qq.com/wiki/develop/miniprogram/server/open_port/port_subscribe.html
     * @param openId
     * @param templateId
     * @param jumpPage
     * @param data  文本内容，key的顺序和模板的顺序一致。
     */
    public String sendSubscriptionMessage(String openId, String templateId, String jumpPage,
                                      LinkedHashMap<String,String> data) {
        String url = "https://api.q.qq.com/api/json/subscribe/SendSubscriptionMessage?access_token="+getAccessToken();

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        JSONObject personJsonObject = new JSONObject();
        personJsonObject.put("access_token", getAccessToken());
        personJsonObject.put("touser", openId);
        personJsonObject.put("template_id", templateId);
        personJsonObject.put("page", jumpPage);

        Map<String, Object> transMap = Maps.transformValues(data, new Function<String, Object>() {
            @Nullable
            @Override
            public Object apply(@Nullable String s) {
                return ImmutableMap.of("value", s);
            }
        });
        personJsonObject.put("data", transMap);

        String param = personJsonObject.toString();
        HttpEntity<String> request =
                new HttpEntity<String>(param, headers);

        String retStr = HttpClientUtils.getRestTemplate().postForObject(url, request, String.class);
        JSONObject json = JSON.parseObject(retStr);
        int errCode = json.getIntValue("errcode");
        String errmsg = json.getString("errmsg");;
        log.info("发送订阅消息 {} 返回 {}, code {} msg {}",
                param, retStr.replace("\n", ""), errCode, errmsg);
        return retStr;
    }

    public String getTokenJsonFromQQ() {
        //调用QQ接口
        String url = "https://api.q.qq.com/api/getToken?grant_type=client_credential&appid="
                + appId
                + "&secret=" + tokenKey;
        String content = null;
        try {
            HttpResponse resp = HttpClientUtils.getRequestMethod(url);
            content = HttpClientUtils.getContent(resp);
            log.info(":>>> AccessToken:{}", content);
        } catch (Exception e) {
            throw new BusinessException("获取AccessToken接口异常");
        }
        if (StringUtils.isBlank(content)) {
            log.error(":>>> HTTP content为空");
            throw new BusinessException("获取AccessToken返回信息为空");
        }
        return content;
    }

    //@TimeType
    public boolean hasIllegal(String text) {
        if (text == null) {
            return false;
        }
        try {
            String accessToken = getAccessToken();
            boolean ok = msgCheckBoolean(accessToken, text);
            return !ok;
        } catch (Exception e) {
            log.error("检查输入内容失败。", e);
            return false;
        }
    }

    public boolean hasIllegal(Resource resource) {
        try {
            String token = getAccessToken();
            boolean checkRet = checkImage(token, resource);
            return !checkRet;
        } catch (Exception e) {
            log.error("检查图片出错。", e);
            return false;
        }
    }
}
